package com.sc.base.common.interceptor.sql;


import cn.hutool.core.date.DateUtil;
import com.sc.base.common.log.*;
import com.sc.base.common.spring.ApplicationConstant;
import org.apache.ibatis.executor.statement.StatementHandler;
import org.apache.ibatis.logging.jdbc.PreparedStatementLogger;
import org.apache.ibatis.mapping.BoundSql;
import org.apache.ibatis.plugin.*;
import org.apache.ibatis.session.ResultHandler;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

import java.lang.reflect.InvocationHandler;
import java.lang.reflect.InvocationTargetException;
import java.lang.reflect.Proxy;
import java.sql.PreparedStatement;
import java.sql.Statement;
import java.util.Date;
import java.util.Properties;
import java.util.function.Predicate;

/**
 * 获取执行的sql信息并打印
 * @author liuwei 2017-11-23
 */
@Intercepts(//@Signature(type = StatementHandler.class, method = "prepare", args = {Connection.class, Integer.class}),
        {@Signature(type = StatementHandler.class, method = "query", args = {Statement.class, ResultHandler.class})
,@Signature(type = StatementHandler.class, method = "update", args = {Statement.class})
})
public class MybatisSQLPerformanceInterceptor implements Interceptor {
    private static final Logger logger = LoggerFactory.getLogger(MybatisSQLPerformanceInterceptor.class);
    public Object intercept(Invocation invocation) throws Throwable {
        SQLRespLog sqlRespLog = null;
        String error = null;
        Long start = System.currentTimeMillis();
        boolean pluginHasError = false;
        PerformanceLogLevel logLevel = null;
        ApplicationConstant applicationConstant = null;
        try {
            sqlRespLog = new SQLRespLog(PerformanceLogType.SQL_RESP, logLevel);
            StatementHandler statementHandler = (StatementHandler) invocation.getTarget();
            BoundSql boundSql = statementHandler.getBoundSql();
            String sql = PerformanceLog.trim(boundSql.getSql());
            String beginDateStr = DateUtil.format(new Date(), "yyyy-MM-dd HH:mm:ss.SSS");
            sqlRespLog.setAddDate(beginDateStr);
            sqlRespLog.setBeginDate(beginDateStr);
            sqlRespLog.setSql(sql);
            Object[] objs = invocation.getArgs();
            Object obj = objs[0];
            InvocationHandler invocationHandler = Proxy.getInvocationHandler(obj);
            if(invocationHandler instanceof PreparedStatementLogger){
                PreparedStatementLogger preparedStatementLogger = (PreparedStatementLogger)invocationHandler;
                PreparedStatement preparedStatement = preparedStatementLogger.getPreparedStatement();
                SQLPerInterceptorUtils.handleDataSourceKey(sqlRespLog,preparedStatement);
            }
        } catch (Exception e) {
            pluginHasError = true;
            logger.error("MybatisSQLPerformanceInterceptor error:{},e:{} " ,e.getMessage(), e);
        }
        Object statement = null;
        try {
            statement =invocation.proceed();
        } catch (InvocationTargetException e) {
            Throwable te = e.getTargetException();
            error = PerformanceLogUtil.logError(te, new SQLExceptionIgnorePredicates(applicationConstant));
            throw e;
        } catch (Exception e) {
            error = PerformanceLogUtil.logError(e, new SQLExceptionIgnorePredicates(applicationConstant));
            throw e;
        } finally {
            try {
                if (!pluginHasError) {
                    long end = System.currentTimeMillis();
                    sqlRespLog.setError(error);
                    String endDateStr = DateUtil.format(new Date(), "yyyy-MM-dd HH:mm:ss.SSS");
                    sqlRespLog.setEndDate(endDateStr);
                    sqlRespLog.setExecuteDate(endDateStr);
                    sqlRespLog.setSqlUsedTime(end - start);
                    logger.info(sqlRespLog.toString());
                }
            } catch (Exception e) {
                logger.error("MybatisSQLPerformanceInterceptor error:{},e:{} " ,e.getMessage(), e);
            }
        }
        return statement;
    }



    public Object plugin(Object target) {
        return Plugin.wrap(target, this);
    }

    public void setProperties(Properties properties0) {
        //this.properties = properties0;
    }

    static class SQLExceptionIgnorePredicates implements Predicate<Throwable> {

        private ApplicationConstant applicationConstant;

        public SQLExceptionIgnorePredicates(ApplicationConstant applicationConstant) {
            this.applicationConstant = applicationConstant;
        }

        @Override
        public boolean test(Throwable e) {

            if(applicationConstant != null && applicationConstant.performanceLogIgnoreSqlDuplicateConstraint) {
                //判断如果是MySQLIntegrityConstraintViolationException并且包含Duplicate entry, 则忽略异常
                if(e.getMessage() != null && e.getMessage().contains("Duplicate entry")) {
                    return true;
                }
            }

            return false;
        }
    }


}
